 package nl.windgazer.arachne.services;

import nl.windgazer.arachne.services.captcha.ImageCaptchaService;
import nl.windgazer.arachne.services.captcha.ImageCaptchaServiceImp;
import nl.windgazer.arachne.services.externalsource.HtmlContentManager;
import nl.windgazer.arachne.services.facebook.FacebookAPISettings;
import nl.windgazer.arachne.services.facebook.FacebookAPISettingsImp;
import nl.windgazer.arachne.services.facebook.FacebookOpenGraphDAO;
import nl.windgazer.arachne.services.facebook.FacebookOpenGraphDAOImp;
import nl.windgazer.arachne.services.facebook.FacebookPrivileges;
import nl.windgazer.arachne.services.messages.ComponentMessagesSourceInterceptor;
import nl.windgazer.arachne.services.messages.MessageBindingFactoryInterceptor;
import nl.windgazer.arachne.services.messages.MessageInterceptEventListener;
import nl.windgazer.arachne.services.messages.MessageInterceptEventListenerImpl;
import nl.windgazer.arachne.services.messages.MessageInterceptorStorage;
import nl.windgazer.arachne.services.messages.MessageInterceptorStorageImp;
import nl.windgazer.arachne.services.yql.YQLConduit;
import nl.windgazer.arachne.services.yql.YQLConduitImp;
import nl.windgazer.arachne.util.ArachneSymbolConstants;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ValidationDecorator;
import org.apache.tapestry5.Validator;
import org.apache.tapestry5.annotations.Path;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Contribute;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.annotations.ServiceId;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.ioc.services.SymbolSource;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.BindingFactory;
import org.apache.tapestry5.services.BindingSource;
import org.apache.tapestry5.services.Context;
import org.apache.tapestry5.services.Cookies;
import org.apache.tapestry5.services.Environment;
import org.apache.tapestry5.services.LibraryMapping;
import org.apache.tapestry5.services.MarkupRenderer;
import org.apache.tapestry5.services.MarkupRendererFilter;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.services.javascript.StylesheetLink;
import org.apache.tapestry5.services.javascript.StylesheetOptions;
import org.apache.tapestry5.services.messages.ComponentMessagesSource;

import com.google.code.zeroclipboard.ZeroclipboardConstants;
import com.joostschouten.common.service.catpcha.CaptchaService;
import com.joostschouten.common.service.catpcha.CaptchaServiceImp;

/**
 * The T5 Module for this code. This Module should be used for factory settings and possibly overriding
 * T5 default services with improved services from T5Arachne. Mostly I would prefer to leave the choice
 * of using T5Arachne services up to the web-application developer.
 * 
 * @author mreuring
 *
 */
public class ArachneModule {

    private static ImageCaptchaService imageCaptchaServiceInstance = null;

    public static void bind(
    		final ServiceBinder binder
    ) {

    	binder.bind(MessageInterceptorStorage.class, MessageInterceptorStorageImp.class).scope(ScopeConstants.DEFAULT);

    	binder.bind(ArachneRenderSupport.class, ArachneRenderSupportImp.class).scope(ScopeConstants.PERTHREAD);
    	binder.bind(FacebookAPISettings.class, FacebookAPISettingsImp.class).scope(ScopeConstants.PERTHREAD);
    	binder.bind(FacebookOpenGraphDAO.class, FacebookOpenGraphDAOImp.class).scope(ScopeConstants.PERTHREAD);
    	binder.bind(MessageInterceptEventListener.class, MessageInterceptEventListenerImpl.class).scope(ScopeConstants.PERTHREAD);

    }

	/**
	 * Contribute this Module to any Tapestry 5 application that has this module on classpath.
	 * @param configuration
	 */
	public void contributeComponentClassResolver(Configuration<LibraryMapping> configuration) {
		configuration.add(new LibraryMapping("arachne", "nl.windgazer.arachne"));
		configuration.add(new LibraryMapping("fb", "nl.windgazer.facebook"));
	}

	/**
	 * Here is where we add additional workers to the Tapestry 5 registry in order to support
	 * annotations.
	 * 
	 * @param configuration
	 * @param locator
	 * @param injectionProvider
	 * @param resolver
	 */
//    public static void contributeComponentClassTransformWorker2(
//            OrderedConfiguration<ComponentClassTransformWorker2> configuration,
//
//            MetaWorker metaWorker,
//
//            ComponentClassResolver resolver)
//    {
//        configuration.addInstance("IncludeCufon", IncludeCufonWorker.class, "after:SetupRender");
//    	
//    	//TODO Figure out how annotations are supposed to work in 5.3, these don't work nomore...
//        //configuration.addInstance("OpenGraphAnnotation", IncludeFaceBookOpenGraphInfoWorker.class, "after:SetupRender");
//        //configuration.addInstance("IncludeFontFace", IncludeFontFaceWorker.class, "after:SetupRender");
////        configuration.addInstance("IncludeOpenGraphMetaInfo", IncludeOpenGraphMetaInfoWorker.class, "before:SetupRender");
////        configuration.addInstance("IncludeFaceBookOpenGraphInfo", IncludeFaceBookOpenGraphInfoWorker.class, "before:SetupRender");
//    }

    public static void contributeClasspathAssetAliasManager(
    		MappedConfiguration<String, String> configuration
    ){
            configuration.add("jsrepository", "nl/windgazer/jsrepository");
            configuration.add("googleCode", "com/google/code");
            configuration.add("ckeditor", "com/ckeditor");
            configuration.add("fontfaces", "com/fontsquirrel/fontfacekits");
            configuration.add("cufon", "com/shoqolate/cufon");
            configuration.add("fbObjectValidation", "de/ka-mediendesign");
    }

	/**
	 * Configuration defaults. Can be overridden in web.xml or on command-line, see t5 documentation for details.
	 * 
	 * @param configuration
	 */
	public void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) {
		configuration.add("arachne.autosave.uri", "WEB-INF/autosave.xml"); //This will be used by the form-autosave mixin default setup.
		configuration.add(ArachneSymbolConstants.TRANSLATOR_ROLE, ArachnePrivilegesImpl.ROLE_ADMIN);
		configuration.add(ArachneSymbolConstants.TRANSLATION_ENABLED, Boolean.toString(true));
		configuration.add(ArachneSymbolConstants.CAPTCHA_CHARS, "ACDEFGHJKLMNPQRSTUVWXYZ2345679@#%&"); //Safe char-set, as in, not confusing and readable
		configuration.add(ArachneSymbolConstants.CAPTCHA_CHARS_MIN, "5");
		configuration.add(ArachneSymbolConstants.CAPTCHA_CHARS_MAX, "7");
		configuration.add(ArachneSymbolConstants.CAPTCHA_CHARS_UPPERCASE, Boolean.toString(true));
		configuration.add(ArachneSymbolConstants.CAPTCHA_CHARS_LOWERCASE, Boolean.toString(false));
		configuration.add(ArachneSymbolConstants.CAPTCHA_IMG_WIDTH, "220");
		configuration.add(ArachneSymbolConstants.CAPTCHA_IMG_HEIGHT, "55");
		configuration.add(ArachneSymbolConstants.CAPTCHA_FONTS, "Arial,Tahoma,Georgia");
		configuration.add(ArachneSymbolConstants.CAPTCHA_FONTS_MIN, "12");
		configuration.add(ArachneSymbolConstants.CAPTCHA_FONTS_MAX, "18");
		configuration.add(ArachneSymbolConstants.CAPTCHA_BACKGROUND, "FFFFFFFF");
		configuration.add(ArachneSymbolConstants.CAPTCHA_COLORS, "00000088,000000FF");

		configuration.add(ArachneSymbolConstants.SIMPLE_VALIDATION_DECORATOR_ENABLED, Boolean.toString(false));
		
		configuration.add(ArachneSymbolConstants.CKEDITOR_BASEDIR, "classpath:/com/ckeditor");
		configuration.add(ArachneSymbolConstants.CKEDITOR_VERSION, "v3_4_1");

		//ZeroClipboard configuration
		configuration.add(ZeroclipboardConstants.BASEDIR_KEY, ZeroclipboardConstants.BASEDIR_DEFAULT);
		configuration.add(ZeroclipboardConstants.VERSION_KEY, ZeroclipboardConstants.VERSION_DEFAULT);

		//External Content configuration
		//By default the path is taken as relative to the application root, when set to false it will be taken as absolute file-path.
		configuration.add(ArachneSymbolConstants.HTML_EC_RELATIVE, ArachneSymbolConstants.HTML_EC_RELATIVE_DEFAULT);
		configuration.add(ArachneSymbolConstants.HTML_EC_PATH, ArachneSymbolConstants.HTML_EC_PATH_DEFAULT);
		configuration.add(ArachneSymbolConstants.EC_ALTERNATE_NAMING, ArachneSymbolConstants.EC_ALTERNATE_NAMING_DEFAULT);

		//YQL configuration
		configuration.add(YQLConduit.YQL_PUBLIC_URL, YQLConduit.YQL_PUBLIC_URL_DEFAULT);
		configuration.add(YQLConduit.YQL_OAUTH_URL, YQLConduit.YQL_OAUTH_URL_DEFAULT);

		//FacebookAPI settings
		configuration.add(FacebookAPISettings.API, FacebookAPISettings.API_NULL);
		configuration.add(FacebookAPISettings.APP, FacebookAPISettings.APP_NULL);
		configuration.add(FacebookAPISettings.APP_SECRET, FacebookAPISettings.APP_NULL);
		configuration.add(FacebookAPISettings.COOKIE_DEFAULT, FacebookAPISettings.COOKIE_DEFAULT_DEFAULT);
		configuration.add(FacebookAPISettings.STATUS_DEFAULT, FacebookAPISettings.STATUS_DEFAULT_DEFAULT);
		configuration.add(FacebookAPISettings.XFBML_DEFAULT, FacebookAPISettings.XFBML_DEFAULT_DEFAULT);
		configuration.add(FacebookAPISettings.ENABLE_SERVICE, Boolean.TRUE.toString());
		configuration.add(FacebookAPISettings.RELOAD_ON_SESSIONCHANGE_DEFAULT, Boolean.FALSE.toString());
		configuration.add(FacebookAPISettings.ENABLE_FACEBOOK_PRIVILEGES, Boolean.FALSE.toString());

	}

    @ServiceId("ContentManagerService")
    @Scope(ScopeConstants.PERTHREAD)
    public static ContentManagerService buildContentManagerService(
          final ThreadLocale threadLocale
        , final Context context
        , final Request request
        , final ArachnePrivileges ap
		, @Inject @Symbol(ArachneSymbolConstants.TRANSLATION_ENABLED) boolean transEnabled
		, @Inject @Symbol(ArachneSymbolConstants.TRANSLATOR_ROLE) String transRole
		, @Inject @Symbol(ArachneSymbolConstants.HTML_EC_RELATIVE) boolean relative
		, @Inject @Symbol(ArachneSymbolConstants.HTML_EC_PATH) String path
    ) {
        return new HtmlContentManager(threadLocale.getLocale(), context, request, ap, transEnabled, transRole, relative, path);
    }

    @ServiceId("YQLDefaultConduit")
    @Scope(ScopeConstants.DEFAULT)
    public static YQLConduit buildYQLConduit(
    			@Inject @Symbol(YQLConduit.YQL_PUBLIC_URL) final String pub
    			, @Inject @Symbol(YQLConduit.YQL_OAUTH_URL) final String oauth
    		)
    {
    	return new YQLConduitImp(pub, oauth);
    }

    @Contribute(ComponentMessagesSource.class)
	public static void provideMessages(final @Value("context:/WEB-INF/t5a_translations.properties") Resource resource
			, final OrderedConfiguration<Resource> configuration
			, final MessageInterceptorStorage mis
	) {
    	//Attempting to keep this file as the verry last!!
		configuration.add(SymbolConstants.APPLICATION_CATALOG + ".T5AT", resource, "after:" + SymbolConstants.APPLICATION_CATALOG);
		
		mis.addKnownMessageResource(configuration, resource);
	}

    /**
     * Decorating the ComponentMessagesSource, this should allow us to intercept all calls to this Interface, however
     * only @Inject Messages is properly intercepted. Any messages being called through ${message:...} are not
     * intercepted by this means, see @see {@link #contributeBindingSource(MappedConfiguration, BindingSource, Environment, boolean)}.
     * A MessageIntercepEventListener must be made available on the Environment to enable the logging and possible
     * use of intercepted messages. Currently the MessagesInterceptorTranslator Mixin makes such a handler available
     * as well as creating the js-code for actual translations.
     * The Symbol 'ArachneSymbolConstants.TRANSLATION_ENABLED' determines wether or not this service will actually be loaded into
     * the stack.
     * 
     */
    public ComponentMessagesSource decorateComponentMessagesSource(
    		final ComponentMessagesSource original,
            @Symbol(ArachneSymbolConstants.TRANSLATION_ENABLED) final boolean translate,
            final @InjectService("Environment") Environment _environment
            )
    {
    	if (translate) return new ComponentMessagesSourceInterceptor(original, _environment);
    	else return original;
    }

    /**
     * Overriding the default ${message:...} binding to one that is aware of my MessageIntercepEventListener code.
     * A MessageIntercepEventListener must be made available on the Environment to enable the logging and possible
     * use of intercepted messages. Currently the MessagesInterceptorTranslator Mixin makes such a handler available
     * as well as creating the js-code for actual translations.
     * The Symbol 'ArachneSymbolConstants.TRANSLATION_ENABLED' determines wether or not this service will actually be loaded into
     * the stack.
     *
     * @param configuration
     * @param bindingSource
     * @param virtualEnvironment
     */
    public static void contributeBindingSource(
        final MappedConfiguration<String, BindingFactory> configuration,
        final BindingSource bindingSource,
        final @InjectService("Environment") Environment virtualEnvironment,
        @Symbol(ArachneSymbolConstants.TRANSLATION_ENABLED) final boolean translate)
    {
    	if (translate) configuration.override("message", new MessageBindingFactoryInterceptor(bindingSource, virtualEnvironment));
    }

    /**
     * Builds the CaptchaService for use in various captcha related components and validators, etc.
     * @return
     */
    @Scope(ScopeConstants.PERTHREAD)
    public CaptchaService buildCaptchaService(SymbolSource symbolSource,
    										ThreadLocale locale) {
    	if (imageCaptchaServiceInstance==null) { //Just want 1 single instance per T5 app...
    		imageCaptchaServiceInstance = new ImageCaptchaServiceImp(symbolSource);
    	}
    	return new CaptchaServiceImp(imageCaptchaServiceInstance,locale.getLocale());
    }
   
    @Scope(ScopeConstants.PERTHREAD)
    public ArachnePrivileges buildArachnePrivileges(
    		@Inject Cookies cookies
    		, @Inject FacebookOpenGraphDAO fog
    		, @Inject Context context
			, @Inject @Symbol(FacebookAPISettings.ENABLE_FACEBOOK_PRIVILEGES) final boolean fbPrivileges
    ){
    	if (fbPrivileges) {
    		return new FacebookPrivileges(fog, context);
    	} else {
    		return new ArachnePrivilegesImpl(cookies);
    	}
    }

	public static void contributeFieldValidatorSource(@SuppressWarnings("rawtypes") MappedConfiguration<String, Validator> configuration,
            @InjectService("Request") final Request request,
            @InjectService("CaptchaService") final CaptchaService captchaService) {
        configuration.add("captcha", new CaptchaValidator(request, captchaService));
    }

    /**
     * Adds page render filters, each of which provides an {@link org.apache.tapestry5.annotations.Environmental}
     * service. Filters often provide {@link org.apache.tapestry5.annotations.Environmental} services needed by
     * components as they render.
     */
    public void contributeMarkupRenderer(
    		OrderedConfiguration<MarkupRendererFilter> configuration
    		, @InjectService("ArachneRenderSupport") final ArachneRenderSupport _arachneSupport
    		, @InjectService("Environment") final Environment _environment
    		, @InjectService("FacebookAPISettings") final FacebookAPISettings _fbAPI
    		, @Symbol(ArachneSymbolConstants.SIMPLE_VALIDATION_DECORATOR_ENABLED) final boolean validationDecoractorEnabled
    		, @Path("classpath:/nl/windgazer/arachne/services/SimpleValidationDecorator.js") final Asset svdJS
    		, @Path("classpath:/nl/windgazer/arachne/services/SimpleValidationDecorator.css") final Asset svdCSS
    		, @Inject @Symbol(FacebookAPISettings.ENABLE_SERVICE) final boolean fbServiceEnabled
    		)
    {

        MarkupRendererFilter renderSupport = new MarkupRendererFilter()
        {

        	public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
            {
                _environment.push(ArachneRenderSupport.class, _arachneSupport);
                renderer.renderMarkup(writer);
                _environment.pop(ArachneRenderSupport.class);
                _arachneSupport.writeMarkup(writer);
            }

        };
        configuration.add("ArachneRenderSupport", renderSupport, "before:DocumentLinker");

        if (fbServiceEnabled) {
	        MarkupRendererFilter facebookAPI = new MarkupRendererFilter() {
				
				public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) {
					_environment.push(FacebookAPISettings.class, _fbAPI);
					renderer.renderMarkup(writer);
					_environment.pop(FacebookAPISettings.class);
					if (_fbAPI.getRender()) {
						_fbAPI.render(writer, _environment);
					}
				}
			};
	        configuration.add("FacebookAPISettings", facebookAPI, "after:ArachneRenderSupport");
        }

        if (validationDecoractorEnabled) {

        	MarkupRendererFilter validationDecorator = new MarkupRendererFilter()
	        {
	            public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
	            {
	                ValidationDecorator decorator = new SimpleValidationDecorator(_environment, writer);
	                _environment.push(ValidationDecorator.class, decorator);

	    	        JavaScriptSupport jss = _environment.peek(JavaScriptSupport.class);
	    	        if (jss!=null) {
	    	        	jss.importJavaScriptLibrary(svdJS);
	    	        	jss.importStylesheet(new StylesheetLink(svdCSS, new StylesheetOptions("screen")));
	    	        }

	    	        renderer.renderMarkup(writer);
	                _environment.pop(ValidationDecorator.class);
	            }
	        };
	        configuration.override("DefaultValidationDecorator", validationDecorator, "after:RenderSupport");

        }

    }

}
